Skip to content

feat(Broadway): propagate layout size classes - #376

Merged
kyleve merged 2 commits into
mainfrom
codex/duo-broadway
Nov 11, 2026
Merged

kyleve merged 2 commits into
mainfrom
codex/duo-broadway

Conversation

@kyleve

@kyleve kyleve commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Posted by an AI agent on kve's behalf.

Summary

Add typed horizontal and vertical size classes to Broadway. UIKit roots observe changes live; SwiftUI roots map their environment into the same traits. Scoped overrides let component styles resolve the container's layout policy.

This is foundation only. It preserves the current main toolchain and screen behavior.

Primary APIs

BHorizontalSizeClass and BVerticalSizeClass expose only compact and regular values. Both default to compact. UIKit unspecified/unknown values and SwiftUI missing/unknown values normalize to compact at the bridge, so stylesheet switches need only two cases. BTraits.system registers both, and bHorizontalSizeClass / bVerticalSizeClass provide scoped SwiftUI overrides.

content
    .bHorizontalSizeClass(.regular)
    .bVerticalSizeClass(.regular)

Stylesheets read context.traits.horizontalSizeClass and verticalSizeClass during slicing. Measured geometry remains a view input. The fallback is an explicit styling policy; known system traits and scoped overrides are preserved without inferring size classes from device identity or bounds.

Independent review found one downstream test matrix using the removed enum case. PR #378 now tests all nine raw UIKit size-class combinations through the Broadway bridge, preserving single-pane fallback coverage.

Stack

1 of 5, based on main. The snapshot foundation follows this PR; reusable navigation and geometry come next. The final app PR describes the complete experience and remaining native release checks.

Testing

  • At current stack tip 2fd6f699853ac4fa592a90d3928f83e836bca635, ./test --all passed on Xcode 27.2: 2,975 iOS unit tests, 28 host macro tests, and 31 architecture tests. SwiftFormat passed. The root PR also passed 141 Broadway tests from an isolated build; an earlier shared-cache build hit stale LifecycleKit deployment-target metadata.
  • The earlier full integration at e9236bb54771a1e144ffbc35ea9076e8f2c4569f passed both CI gates, including all image suites. The current change normalizes unavailable Broadway size classes to compact and updates the downstream UIKit-input regression matrix; reference PNGs and renderer settings are unchanged. New full gates are running on the current tip: GitHub Actions and CircleCI pipeline 2166.
  • Visual review covers every changed reference, including full-height content, accessibility annotations, large text, and theme panels. Reference PNG metadata and comparison tolerances are preserved.
  • Independent review covered all five stack slices and the final capture, navigation, and inset changes. Focused mutation tests demonstrate that stale-inset delivery and off-screen accessibility regressions fail without their fixes.
  • Two tall Year accessibility images retain a pre-existing marker offset and incomplete native-toolbar legends. The capture follow-up is documented below; it is tracked in STU-325; these images do not prove live VoiceOver behavior.
  • Native Duo 27.2 geometry re-measurement and interactive device checks remain pending. Synthetic regular-simulator coverage does not establish native system-bar geometry or spoken VoiceOver behavior.

Backlog reconciliation

The 13 preserved Duo records are tracked in Linear. STU-318 owns adoption and its six implementation sub-issues. This slice relates to STU-318. Unmerged implementation remains In Review.

Remaining Backlog work: native Duo release validation (STU-316), intermittent Locations settling diagnostics (STU-317), and tall Year accessibility annotations (STU-325). Existing STU-114 and STU-252 now distinguish the partial fixes from their remaining requirements; both retain their priorities, labels, and open state.

@kyleve
kyleve added this pull request to stack #379 October 16, 2026 14:07
@kyleve kyleve changed the title Add live layout size classes to Broadway feat(Broadway): propagate layout size classes Oct 16, 2026

/// The horizontal space available to a component. Unspecified preserves an unknown system value.
public enum BHorizontalSizeClass: Hashable, Sendable {
case unspecified

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t love including unspecified here; it means every check has to include this check. Any way to avoid it? Can we calculate our size class on our available space instead?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posted by an AI agent on kve's behalf.

Implemented in e698652. Both Broadway size-class enums now expose only .compact and .regular. UIKit .unspecified/unknown values and SwiftUI nil/unknown values normalize to .compact at the bridge; the default on both axes is also compact. Known system values, live observation, and scoped overrides remain intact. This gives styling code two-case switches without deriving system traits from measured bounds.

Rebased and pushed all five stack PRs. In #378, 6a04f77 preserves all nine UIKit input combinations in the pane-policy regression, including the unavailable-value fallback. Validation passed: 141 Broadway tests on this root PR, then 2,975 iOS unit tests, 28 host macro tests, and 31 architecture tests at stack tip 2fd6f699853ac4fa592a90d3928f83e836bca635. SwiftFormat passed. The fresh full CI gates, including snapshots, are running.

Register typed horizontal and vertical traits, map SwiftUI root inputs, and expose scoped stylesheet overrides. Verify repeated trait changes in a mounted host.

Validation: BroadwayCoreTests (118 tests), BroadwayUITests (20 tests), and architecture checks passed on pinned Xcode 27.0.
@kyleve
kyleve force-pushed the codex/duo-broadway branch from 9611da6 to 329a963 Compare November 9, 2026 22:30
Expose only compact and regular on both axes. Normalize unspecified UIKit values and missing SwiftUI values at the bridge, and keep defaults, live observation, and scoped overrides consistent.

Validation: 141 Broadway tests passed from clean artifacts; architecture checks and SwiftFormat passed. Shared-cache attempts failed on stale LifecycleKit deployment metadata; isolated build and execution passed.
@kyleve
kyleve enabled stacked auto-merge (squash) November 11, 2026 17:12
@kyleve
kyleve merged commit b3546eb into main Nov 11, 2026
22 checks passed
kyleve added a commit that referenced this pull request Nov 11, 2026
> _Posted by an AI agent on kve's behalf._

## Summary

Add explicit frame layout traits and recorded Duo profiles to the Xcode
27.2 baseline from main. Apply requested content clearance after UIKit
finalizes containment and native geometry.

This layer retains the existing Where navigation. Its image references
establish the new capture baseline before the adaptive app changes.
Calendar month sections, day rows, and Timeline rows measure eagerly so
full-content captures include the final rows; the selection and tabletop
changes remain in the app PR. Eager Timeline measurement removes blank
tails caused by lazy height estimates in a bottom-anchored scroll view.
Settings waits for native search chrome to settle before measuring its
full height. Existing scrolling onboarding and Devices fixtures use
full-content frames. Theme choices also fall back to a vertical layout
when narrow, and large-text Logged Days rows retain their dates, notes,
and labels.

## Screenshots

Developer Overlay, light iPhone full-content capture. Retaining the
scene restores uniform native glass without changing the menu's styling,
dimensions, or content.

| Before | After |
|---|---|
| <img alt="Detached-scene menu with bright bands and a hard material
cutoff" width="260"
src="https://media.githubusercontent.com/media/kyleve/Stuff/9e759fc8b0ac8db6569bfb03d49cdafd54c7ad2c/Where/WhereUI/SnapshotTests/__Snapshots__/DeveloperOverlaySnapshotTests/developerOverlay.MenuTopLeading_iPhone.png">
| <img alt="Scene-backed menu with uniform glass and complete content"
width="260"
src="https://media.githubusercontent.com/media/kyleve/Stuff/f05ca62400c415631e21c5e649ce655700d9f18a/Where/WhereUI/SnapshotTests/__Snapshots__/DeveloperOverlaySnapshotTests/developerOverlay.MenuTopLeading_iPhone.png">
|

Before: `9e759fc8`. After: `f05ca6240`; all six overlay captures
reproduced byte for byte.

## Primary APIs

- `SnapshotConfiguration.Frame.LayoutTraits` owns horizontal and
vertical size classes and interface idiom. Configuration-level layout
traits remain supported and take precedence over frame defaults. Phone
and iPad presets carry truthful traits without changing their filenames.
- `Frame.scenePolicy` controls capture scene retention without renaming
references. Both configured assertions and hosted PNG export honor it.
- `Frame.viewport` captures bounded shell composition. It must be paired
with full-content captures of scrolling panes.
- The four Duo profiles use measured native geometry. The module README
retains the measurement date, runtime/build, collection method, and
validation limits; the unused raw JSON dump is removed. `.narrowWindow`
covers a synthetic multitasking container.

```swift
let configuration = SnapshotConfiguration(device: .duoInnerPortrait)
configuration.applyTraits(to: host)
```

Previews and UIKit capture use the same mapping. Conversion from
configuration traits to frame traits uses exhaustive switches for both
size classes and idiom, so new enum cases require an explicit mapping.
Custom frames can supply `layoutTraits`; component frames inherit the
host's traits.

## Capture and tooling boundaries

All capture entry points acquire one lock before host discovery,
validation, and preparation. Fixed, regular-width phone captures retain
their scene so native bar geometry stays stable. Other synthetic
captures detach the marked window by default, preserving compact title
space and anchoring full-content search to expanded content. Capture and
measurement hosts reconcile their effective insets after containment and
layout, with another reconciliation after settling. The predicate uses
content-controller traits, not pixel width or simulator identity. Frames
can explicitly retain the scene when native chrome requires it.
Periscope and Developer Overlay use this option without changing their
configured traits, insets, or sizing. Developer Overlay's native glass
otherwise develops bright bands and a hard material cutoff. Retention
can expose physical reserved regions, so general presets keep the
automatic policy. Both paths restore scene and presentation state on
success or failure. A nil inset override retains native geometry.

Only the captured root overrides its safe-area getter, during final
pixel rendering. Native getters remain active during containment,
measurement, and settling so inset changes propagate to children. Root
pinning protects translated tiles. Overriding ancestors doubled
asymmetric insets after measurement rehosting. Annotation padding has
zero safe-area insets to avoid moving centered content. Tall tiled Year
annotations retain the pre-existing limitation listed below. Regressions
cover these boundaries, already-mounted inputs, compact and regular
title readability, native bar stability, tiling, and failure cleanup.
Full-content captures reset restored scroll offsets after readiness;
viewport captures preserve them.

Accessibility subjects stay mounted during readiness and settling.
Ordinary captures retain their controller parent during parsing; only
upstream tiling temporarily detaches it. Annotation wrappers grow only
after the final accessibility parse. Synthetic viewports recover native
navigation-bar metadata synchronously, restore window geometry before
parsing, and restore the original getters on every exit.

Pixel settling retains main’s normalized pixel comparison and retry
policy. Final reference-comparison tolerances remain unchanged. Hosted
resize, restoration, full-content, PNG export, and cancellation checks
cover the shared pipeline.

Snapshot shard discovery accepts literal multiline source arrays with
shared support files. Unsupported declarations fail without borrowing a
following target's sources. The existing shard assignments stay
unchanged.

## Compatibility and review focus

- The stack requires iOS 27.1. Development and CI use Xcode and
simulator 27.2. The native macOS deployment target is unchanged.
- Review the capture isolation and frame-trait API changes before the
references. The fix addresses stale inset compensation: requested
top/bottom values of 5/9 previously reached native children as 0/0.
- The app integration in #372 fixes a separate stale developer-overlay
inset delivery. Native accessibility capture corrections belong to this
foundation layer; later app layers own their navigation and search
behavior.
- The Duo profiles retain their recorded 27.1 measurements.
Re-measurement on the native 27.2 Duo runtime remains an explicit
release-validation item.
- Theme descriptions retain three lines of minimum space and grow for
large text. The shared picker no longer truncates the Flat description
at AX5.

## Testing

- At `42ab5b40e`, `./test --review --only SnapshotKitTests --only
WhereUISnapshotTests/DeveloperOverlaySnapshotTests` passed all 56
SnapshotKit tests and the Developer Overlay image suite (all six
captures matched byte for byte). Architecture and SwiftFormat passed.
Review fixes preserve the existing mappings and presets; no references
changed.
- Full gates on the current head `42ab5b40e`: [GitHub
Actions](https://github.com/kyleve/Stuff/actions/runs/43081928022) and
[CircleCI pipeline
2195](https://app.circleci.com/pipelines/circleci/HfkN3cJ2hk23YfG42g3oS1/E7hn5FALiSX67sknpk9SZu/2195).

- At `f05ca6240`, the full run passed 2,922 iOS unit tests and 28 host
macro tests. All 71 snapshot tests are covered by the full run plus
focused assertions after review; 679 affected WhereUI tests also passed
after the eager Timeline change.
- `./test --no-generate --review --timings --only WhereUITests` with the
seven affected snapshot suites verified the refreshed references. A
final `./test --no-generate --no-build --review --only
WhereUISnapshotTests/DeveloperOverlaySnapshotTests` repeated all six
overlay captures byte for byte. Architecture checks and SwiftFormat
passed.
- Full gates for the capture-repair baseline (before the
exhaustive-conversion refactor and documentation cleanup): [GitHub
Actions](https://github.com/kyleve/Stuff/actions/runs/43079785790) and
[CircleCI pipeline
2188](https://app.circleci.com/pipelines/circleci/HfkN3cJ2hk23YfG42g3oS1/E7hn5FALiSX67sknpk9SZu/2188).

The capture fixes and reference migration are verified at this PR's own
layer. A green stack tip cannot establish that an earlier PR's renderer
and references agree. Layer-specific Timeline, Settings, and Year
references retain the behavior present here rather than borrowing later
adaptive UI semantics.

- Independent source review covered capture containment, final
annotation sizing, native-bar getter restoration, Settings measurement,
and eager Timeline layout. Visual review covered the refreshed
references and preserved comparison-sheet metadata; comparison
tolerances are unchanged.
- Two tall Year accessibility images retain a pre-existing marker offset
and incomplete native-toolbar legends
([STU-325](https://linear.app/kve/issue/STU-325)).
- Native Duo 27.2 geometry re-measurement and interactive device checks
remain pending. Synthetic regular-simulator coverage does not establish
native system-bar geometry or spoken VoiceOver behavior.

## Stack

First active layer, based on main after
[#376](#376) merged. This supplies
the capture foundation for #378 and #372. Capture corrections and their
references land here; adaptive corrections and references land with the
app integration.

## Backlog reconciliation

The 13 preserved Duo records are tracked in Linear.
[STU-318](https://linear.app/kve/issue/STU-318) owns adoption and its
six implementation sub-issues. This slice relates to
[STU-319](https://linear.app/kve/issue/STU-319),
[STU-320](https://linear.app/kve/issue/STU-320),
[STU-327](https://linear.app/kve/issue/STU-327). Unmerged implementation
remains In Review.

Remaining Backlog work: native Duo release validation
([STU-316](https://linear.app/kve/issue/STU-316)), intermittent
Locations settling diagnostics
([STU-317](https://linear.app/kve/issue/STU-317)), and tall Year
accessibility annotations
([STU-325](https://linear.app/kve/issue/STU-325)). Existing
[STU-114](https://linear.app/kve/issue/STU-114) and
[STU-252](https://linear.app/kve/issue/STU-252) now distinguish the
partial fixes from their remaining requirements; both retain their
priorities, labels, and open state.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant